/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef HTTP2_HTTP2_COMMON_H
#define HTTP2_HTTP2_COMMON_H
#include "http2_handle_shake.h"
using namespace std;

enum { IO_NONE, WANT_READ, WANT_WRITE };
void (*g_postEvent)(int type, void *data, int len, int rpcType, string requestId);

void MessageCallback(void (*postEventFunction)(int type, void *data, int len, int rpcType, string requestId))
{
    g_postEvent = postEventFunction;
}

ssize_t SendCallback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *userData)
{
    if (!userData) {
        LOGE("Error SendCallback NULL");
        return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
    struct Connection *connection = nullptr;
    int rv = 0;
    connection = (struct Connection *)userData;
    connection->wantIo = IO_NONE;
    if (IsDestroyed(connection->reqId)) {
        return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
    int len = static_cast<int>(length);
    if (connection->writeBuffLen <= 0) {
        return 0;
    }
    if (!SSL_is_init_finished(connection->ssl)) {
        return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
    if (SSL_get_shutdown(connection->ssl) & SSL_RECEIVED_SHUTDOWN) {
        return NGHTTP2_ERR_EOF;
    }
    if (connection->ssl && data) {
        rv = SSL_write(connection->ssl, data, len);
    }
    if (rv <= 0) {
        if (IsDestroyed(connection->reqId)) {
            return 0;
        }
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->wantIo = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            return NGHTTP2_ERR_WOULDBLOCK;
        } else {
            return NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    }
    return rv;
}

ssize_t RecvCallback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *userData)
{
    if (!userData) {
        LOGE("Error RecvCallback NULL");
        return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
    int rv = 0;
    struct Connection *connection = (struct Connection *)userData;
    connection->wantIo = IO_NONE;
    if ((connection == nullptr) || (connection->ssl == nullptr) ||
        (buf == nullptr) || IsDestroyed(connection->reqId)) {
        return 0;
    }
    int len = static_cast<int>(length);
    if (!SSL_is_init_finished(connection->ssl)) {
        return NGHTTP2_ERR_CALLBACK_FAILURE;
    }
    if (SSL_get_shutdown(connection->ssl) & SSL_RECEIVED_SHUTDOWN) {
        return NGHTTP2_ERR_EOF;
    }
    rv = SSL_read(connection->ssl, buf, len);
    if (rv < 0) {
        LOGE("RecvCallback SSL_read %d", rv);
        if (IsDestroyed(connection->reqId)) {
            return 0;
        }
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->wantIo = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            rv = NGHTTP2_ERR_WOULDBLOCK;
        } else {
            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    } else if (rv == 0) {
        rv = NGHTTP2_ERR_EOF;
        LOGE("RecvCallback NGHTTP2_ERR_EOF");
    }
    return rv;
}

int OnFrameRecvCallback(nghttp2_session *session, const nghttp2_frame *frame, void *userData)
{
    if (!userData || !frame) {
        LOGE("Error OnFrameRecvCallback NULL");
        return 0;
    }
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    LOGE("OnFrameRecvCallback %d reqId %s recvLargeDataFlags %d", frame->hd.type,
        connection->reqId.c_str(), connection->recvLargeDataFlags);
    if (IsDestroyed(connection->reqId)) {
        return 0;
    }
    if (frame->hd.type == NGHTTP2_PING) {
        bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
        if (ack) {
            int pingRv = nghttp2_submit_ping(connection->session, NGHTTP2_FLAG_ACK, frame->ping.opaque_data);
        }
    }
    if (frame->hd.type == NGHTTP2_DATA && !connection->recvLargeDataFlags) {
        g_callJsCheckDataManagerMap[connection->reqId] = false;
        LOGE("OnFrameRecvCallback NGHTTP2_DATA reqId %s", connection->reqId.c_str());
        g_postEvent(HTTP_STREAM_CALLBACK_TYPE_RESPONSE_DATA_TRUNCK,
            connection->responseData.data(), connection->responseData.size(), connection->rpcType, connection->reqId);
        connection->responseData.clear();
    }
    if (frame->hd.type == NGHTTP2_HEADERS && connection->recvLargeDataFlags && (connection->responseData.size() > 0)) {
        g_callJsCheckDataManagerMap[connection->reqId] = false;
        LOGE("OnFrameRecvCallback NGHTTP2_HEADERS reqId %s", connection->reqId.c_str());
        g_postEvent(HTTP_STREAM_CALLBACK_TYPE_RESPONSE_DATA_TRUNCK,
            connection->responseData.data(), connection->responseData.size(), connection->rpcType, connection->reqId);
        connection->responseData.clear();
    }
    return 0;
}

int OnStreamCloseCallback(nghttp2_session *session, int32_t streamId, uint32_t errorCode, void *userData)
{
    LOGE("OnStreamCloseCallback");
    if (!userData) {
        LOGE("Error OnStreamCloseCallback NULL");
        return 0;
    }
    struct Connection *connection = nullptr;
    connection = static_cast<struct Connection *>(userData);
    if (IsDestroyed(connection->reqId)) {
        return 0;
    }
    if (g_streamCloseManagerMap.Find(connection->reqId) == g_streamCloseManagerMap.End()) {
        g_streamCloseManagerMap.Insert(connection->reqId, true);
    }
    if (connection->rpcType == UNARY_STREAM_RPC &&
        (g_callJsCheckDataManagerMap.Find(connection->reqId) != g_callJsCheckDataManagerMap.End())) {
        //当有data数据时直接return不再执行下面的回调
        return 0;
    }
    if (g_responseHeadersManagerMap.Find(connection->reqId) == g_responseHeadersManagerMap.End()) {
        return 0;
    }
    ConcurrentHashMap<std::string, std::string> resultMap;
    {
        auto it = g_responseHeadersManagerMap.Find(connection->reqId);
        if (it == g_responseHeadersManagerMap.End()) {
            return 0;
        }
        auto range = g_responseHeadersManagerMap.EqualRange(connection->reqId);
        for (auto responseIt = range.first; responseIt != range.second; ++responseIt) {
            const auto& innerMap = responseIt->second;
            resultMap.Insert(innerMap.begin(), innerMap.end());
        }
    }
    const string headerString = UnorderedMapToJsonString(resultMap);
    resultMap.Clear();
    LOGE("OnStreamCloseCallback success header %s reqId %s ", headerString.c_str(), connection->reqId.c_str());
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_RESPONSE, const_cast<char *>(headerString.c_str()),
        0, connection->rpcType, connection->reqId);
    return 0;
}

int OnDataChunkRecvCallback(nghttp2_session *session, uint8_t flags, int32_t streamId, const uint8_t *data,
                            size_t len, void *userData)
{
    if (!userData) {
        LOGE("Error OnDataChunkRecvCallback NULL");
        return 0;
    }
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    LOGE("OnDataChunkRecvCallback rpcType %d len %d reqId %s", connection->rpcType, len, connection->reqId.c_str());
    if (IsDestroyed(connection->reqId)) {
        return 0;
    }
    connection->responseData.insert(connection->responseData.end(), data, data + len);
    LOGE("OnDataChunkRecvCallback success reqId %s", connection->reqId.c_str());
    return 0;
}

int OnHeaderCallback(nghttp2_session *handle, const nghttp2_frame *frame, nghttp2_rcbuf *name,
                     nghttp2_rcbuf *value, uint8_t flags, void *userData)
{
    if (!userData || !name || !value) {
        LOGE("Error OnHeaderCallback NULL");
        return 0;
    }
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->reqId)) {
        return 0;
    }
    if (connection->rpcType == UNARY_STREAM_RPC &&
        (g_callJsCheckDataManagerMap.Find(connection->reqId) != g_callJsCheckDataManagerMap.End())) {
        //当有data数据时直接return不再执行下面的回调
        return 0;
    }
    auto nameBuf = nghttp2_rcbuf_get_buf(name);
    auto valueBuf = nghttp2_rcbuf_get_buf(value);
    if (!nameBuf.base || !valueBuf.base) {
        return 0;
    }
    std::string headerName(reinterpret_cast<char*>(nameBuf.base), nameBuf.len);
    std::string headerValue(reinterpret_cast<char*>(valueBuf.base), valueBuf.len);
    std::unordered_map<std::string, std::string> headerMap;
    {
        std::lock_guard<std::mutex> lock(g_responseHeadersMapMutex);
        if (!headerName.empty() && !headerValue.empty()) {
            headerMap[headerName] = headerValue;
        }
        if (!headerMap.empty() && !connection->reqId.empty()) {
            g_responseHeadersManagerMap.Insert(connection->reqId, headerMap);
        }
    }
    return 0;
}

int OnNghttpError(nghttp2_session *handle, int errorCode, const char *message, size_t len,
                  void *userData)
{
    if (!userData || !message) {
        LOGE("Error OnNghttpError NULL");
        return 0;
    }
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->reqId)) {
        return 0;
    }
    LOGE("OnNghttpError errcode %d msg %s reqId %s", errorCode, message, connection->reqId.c_str());
    HttpStreamErrorCallback errCallBack;
    errCallBack.errCode = errorCode;
    errCallBack.msg = message;
    ConcurrentHashMap<std::string, std::string> errorMapInner;
    errorMapInner["message"] = (char *)message;
    errorMapInner["code"] = (char *)std::to_string(errorCode).c_str();
    std::string result = UnorderedMapToJsonString(errorMapInner);
    void* data = static_cast<void *>(const_cast<char*>(result.c_str()));
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_ERROR, data, 0, connection->rpcType, connection->reqId);
    if (g_streamCloseManagerMap.Find(connection->reqId) == g_streamCloseManagerMap.End()) {
        g_streamCloseManagerMap.Insert(connection->reqId, true);
    }
    return 0;
}

void SetupNghttp2Callbacks(nghttp2_session_callbacks *callbacks)
{
    nghttp2_session_callbacks_set_on_header_callback2(callbacks, OnHeaderCallback);
    nghttp2_session_callbacks_set_send_callback2(callbacks, SendCallback);
    nghttp2_session_callbacks_set_recv_callback2(callbacks, RecvCallback);
    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, OnFrameRecvCallback);
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, OnStreamCloseCallback);
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, OnDataChunkRecvCallback);
    nghttp2_session_callbacks_set_error_callback2(callbacks, OnNghttpError);
}

void CtlPoll(struct pollfd *pollfd, nghttp2_session *session, int wantIo)
{
    pollfd->events = 0;
    if (nghttp2_session_want_read(session) || wantIo == WANT_READ) {
        pollfd->events |= POLLIN;
    }
    if (nghttp2_session_want_write(session) || wantIo == WANT_WRITE) {
        pollfd->events |= POLLOUT;
    }
}

HandleIoResult HandleIo(Connection *connection, string reqId)
{
    LOGE("HandleIo session %p reqId %s", connection->session, reqId.c_str());
    HandleIoResult handleIoResult;
    if (!connection->session || IsDestroyed(reqId)) {
        handleIoResult.code = -1;
        return handleIoResult;
    }
    int sessionResult = 0;
    sessionResult = nghttp2_session_recv(connection->session);
    if (sessionResult != 0) {
        LOGE("nghttp2_session_recv %d reqId %s ", sessionResult, reqId.c_str());
        handleIoResult.code = sessionResult;
        handleIoResult.isRecv = true;
        return handleIoResult;
    }
    sessionResult = nghttp2_session_send(connection->session);
    if (sessionResult != 0) {
        LOGE("nghttp2_session_send %d reqId %s", sessionResult, reqId.c_str());
        handleIoResult.code = sessionResult;
        handleIoResult.isRecv = false;
        return handleIoResult;
    }
    LOGE("HandleIo session end sessionResult %d reqId %s sessionResult %d",
        sessionResult, reqId.c_str(), sessionResult);
    handleIoResult.code = sessionResult;
    return handleIoResult;
}

ssize_t DataSourceReadCallback(nghttp2_session *session, int32_t streamId, uint8_t *buf, size_t len,
                               uint32_t *dataFlags, nghttp2_data_source *source, void *userData)
{
    Connection *connection = (struct Connection *)userData;
    if (CheckReqId(&connection->reqId) && IsDestroyed(connection->reqId.c_str())) {
        *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
        return 0;
    }
    if (connection->rpcType != DUPLEX_STREAM_RPC || connection->dataFlags) {
        *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
    } else {
       *dataFlags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
    }
    if (CheckReqId(&connection->reqId) && (connection->fd > 0 && connection->fd < NUM_1000) &&
        connection->canSendData) {
        if ((connection->rpcType == DUPLEX_STREAM_RPC)  && (connection->streamId != streamId)) {
            return 0;
        }
        std::copy(connection->buffer, connection->buffer + connection->writeBuffLen, buf);
        connection->canSendData = false;
        LOGE("DataSourceReadCallback send success write_buff_len %d reqId %s fd %d", connection->writeBuffLen,
            connection->reqId.c_str(), connection->fd);
        return connection->writeBuffLen;
    } else {
        connection->writeBuffLen = 0;
        return 0;
    }
}

Connection SendData(Connection *connection, void *bufferData, int buffLen, string extendParams)
{
    LOGE("napi-->SendData reqId %s host %s path %s buffLen %d rpcType %d", connection->reqId.c_str(),
         connection->host.c_str(), connection->path.c_str(), buffLen, connection->rpcType);
    std::unordered_map<std::string, std::string> parseHeadersMap = ParseReqExtendParams(extendParams);
    std::vector<nghttp2_nv> nva = RequestHeader(connection->host, connection->path);
    bool dataFlags = false;
    bool recvLargeDataFlags = false;
    for (const auto &header : parseHeadersMap) {
        const std::string key = header.first;
        const std::string value = header.second;
        LOGE("SendData header key %s value %s", key.c_str(), value.c_str());
        if (CheckStringIsArray(value)) {
            const std::string valueTemp = GetFirstIndexFromArray(value);
            if (key == "dataflags") {
                dataFlags = NapiUtil::StringToBool(valueTemp);
            }
            if (key == "recvlargedataflags") {
                recvLargeDataFlags = NapiUtil::StringToBool(valueTemp);
            }
            nva.push_back(CreateNV(key.c_str(), valueTemp.c_str()));
        } else {
            nva.push_back(CreateNV(key.c_str(), value.c_str()));
        }
    }
    Connection sendDataResult;
    if (!connection->session || !CheckReqId(&connection->reqId)) {
        LOGE("Error SendData session %p  reqId %s ", connection->session, connection->reqId.c_str());
        return sendDataResult;
    }
    nghttp2_data_source reqBuffer;
    reqBuffer.ptr = bufferData;
    nghttp2_data_provider2 data_prvd = {reqBuffer, DataSourceReadCallback};
    connection->writeBuffLen = buffLen;
    connection->dataFlags = dataFlags;
    connection->buffer = static_cast<uint8_t *>(bufferData);
    connection->canSendData = true;
    connection->recvLargeDataFlags = recvLargeDataFlags;
    int streamId = nghttp2_submit_request2(connection->session, nullptr, nva.data(),
        nva.size(), &data_prvd, connection);
    LOGE("SendData end streamId %d", streamId);
    sendDataResult.dataFlags = dataFlags;
    sendDataResult.recvLargeDataFlags = recvLargeDataFlags;
    sendDataResult.streamId = streamId;
    return sendDataResult;
}

void SendDataByStreamId(Connection *connection, void *bufferData, int buffLen)
{
    if (!connection->session || IsDestroyed(connection->reqId)) {
        LOGE("Error SendDataByStreamId");
        return;
    }
    LOGE("SendDataByStreamId reqId %s host %s path %s buffLen %d steamId %d", connection->reqId.c_str(),
         connection->host.c_str(), connection->path.c_str(), buffLen, connection->streamId);
    if (buffLen <= 0 || buffLen > MAX_ALLOC_SIZE) {
        LOGE("Invalid buffer length: %d", buffLen);
        return;
    }
    //拷贝内存副本重新赋值防止出现脏数据，解决问题grpc:received message larger than max
    uint8_t* byteData = static_cast<uint8_t*>(bufferData);
    auto bufferCopy = std::make_unique<uint8_t[]>(buffLen);
    std::copy(byteData, byteData + buffLen, bufferCopy.get());
    connection->buffer = bufferCopy.release();
    connection->writeBuffLen = buffLen;
    connection->rpcType = DUPLEX_STREAM_RPC;
    connection->canSendData = true;
}

int HandleSessionClient(Connection *connection)
{
    nghttp2_session_callbacks *callbacks;
    int rv = nghttp2_session_callbacks_new(&callbacks);
    if (rv != 0) {
        LOGE("nghttp2_session_callbacks_new %d", rv);
        return rv;
    }
    SetupNghttp2Callbacks(callbacks);
    nghttp2_option *options;
    nghttp2_option_new(&options);
    nghttp2_option_set_no_auto_window_update(options, 0);
    connection->callbacks = callbacks;
    rv = nghttp2_session_client_new3(&connection->session, callbacks, connection, options, nullptr);
    if (rv != 0) {
        LOGE("nghttp2_session_client_new %d", rv);
        nghttp2_session_callbacks_del(callbacks);
        return rv;
    }
    return rv;
}

int HandleSSLShake(Connection *connection, int fd, string hostAddress)
{
    SSL_CTX *sslCtx;
    SSL *ssl;
    sslCtx = SSL_CTX_new(TLS_client_method());
    if (sslCtx == nullptr) {
        LOGE("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), nullptr));
    }
    if (!InitSslCtx(sslCtx, connection)) {
        return -1;
    }
    ssl = SSL_new(sslCtx);
    if (ssl == nullptr) {
        LOGE("SSL_new %s", ERR_error_string(ERR_get_error(), nullptr));
    }
    int rv = OpenSSLConnectHandle(ssl, fd, hostAddress);
    if (rv < 0) {
        LOGE("SSL/TLS handshake failed rv %d", rv);
        return rv;
    }
    connection->ssl = ssl;
    connection->sslCtx = sslCtx;
    connection->wantIo = IO_NONE;
    connection->fd = fd;
    MakeNonBlock(fd);
    SetTcpNodelay(fd);
    rv = HandleSessionClient(connection);
    if (rv != 0) {
        LOGE("HandleSessionClient %d", rv);
        return rv;
    }
    nghttp2_settings_entry iv[] = {
        {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, NUM_16384},
        {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, NUM_100}
    };
    rv = nghttp2_submit_settings(connection->session, NGHTTP2_FLAG_NONE, iv, sizeof(iv)/sizeof(iv[0]));
    if (rv != 0) {
        LOGE("nghttp2_submit_settings %d", rv);
        return rv;
    }
    return rv;
}

void FreeResource(Connection *connection)
{
    if (!connection || connection->fd <= 0 || connection->fd > NUM_1000 ||
        connection->reqId.empty() || !CheckReqId(&connection->reqId)) {
        return;
    }
    g_sessionManagerMap[connection->reqId] = 1;
    LOGE("FreeResource fd %d reqId %s reqIdLen %d", connection->fd,
         connection->reqId.c_str(), connection->reqId.length());
    if (g_freeManagerMap.Find(connection->reqId) != g_freeManagerMap.End()) {
        g_freeManagerMap.Erase(connection->reqId);
        return;
    }
    g_freeManagerMap[connection->reqId] = true;
    if (connection->callbacks) {
        nghttp2_session_callbacks_del(connection->callbacks);
    }
    if (connection->session) {
        nghttp2_session_del(connection->session);
    }
    if (connection->ssl) {
        SSL_shutdown(connection->ssl);
        SSL_free(connection->ssl);
    }
    if (connection->sslCtx) {
        SSL_CTX_free(connection->sslCtx);
    }
    if (connection->fd > 0) {
        shutdown(connection->fd, SHUT_WR);
        close(connection->fd);
    }
    connection->session = nullptr;
    connection->ssl = nullptr;
    connection->sslCtx = nullptr;
    connection->responseData.clear();
    connection->responseData.shrink_to_fit();
    LOGE("FreeResource success fd %d reqId %s", connection->fd, connection->reqId.c_str());
}
#endif